{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true,
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "# Tutorial on how to couple a power grid and a gas network by a power-to-gas plant and a fuel cell"
   ]
  },
  {
   "cell_type": "markdown",
   "source": [
    "In this tutorial, a power network and a gas network are coupled by a power-to-gas unit (P2G) and\n",
    "a gas-to-power unit (G2P), e.g. a fuel cell. The P2G and G2P have an input value that is set in one\n",
    "network (power or gas consumption, respectively). During the simulation, the output value is calculated\n",
    "by applying efficiency factors and is written then to the other network.\n",
    "\n",
    "There are three basic steps:\n",
    "1. bringing the networks together in a multinet-frame\n",
    "1. adding elements for the P2G and G2P units and coupling controller\n",
    "1. executing the coupled power and pipe flow"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Creating a multi-net\n",
    "First, we import some example networks and set the fluid for the gas net and\n",
    "P2G conversion."
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "from pandapower import networks as e_nw\n",
    "net_power = e_nw.example_simple()\n",
    "\n",
    "import pandapipes as ppipes\n",
    "from pandapipes import networks as g_nw\n",
    "\n",
    "net_gas = g_nw.gas_meshed_square()\n",
    "# some adjustments:\n",
    "net_gas.junction.pn_bar = net_gas.ext_grid.p_bar = 30\n",
    "net_gas.pipe.diameter_m = 0.4\n",
    "\n",
    "# set fluid:\n",
    "ppipes.create_fluid_from_lib(net_gas, 'hydrogen', overwrite=True)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Then, we create a 'multinet'. It serves as a container for multiple networks to enable\n",
    "coupled simulation. Each net in the multinet has to have an unique name. Any name can be chosen - default\n",
    " names are 'power' and 'gas', but 'net1' and 'net2' would work just as fine. The number of\n",
    " networks in the multinet is not limited."
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "from pandapipes.multinet.create_multinet import create_empty_multinet, add_net_to_multinet\n",
    "multinet = create_empty_multinet('tutorial_multinet')\n",
    "add_net_to_multinet(multinet, net_power, 'power')\n",
    "add_net_to_multinet(multinet, net_gas, 'gas')"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "The individual networks can be called from the multinet or by the variable name - the result is\n",
    "identical:"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "print(multinet.nets['power'])\n",
    "print(multinet.nets['gas'])"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "print(net_power)\n",
    "print(net_gas)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "print(net_power is multinet.nets['power'])\n",
    "print(net_gas is multinet.nets['gas'])"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Thus, changes to the networks will be found at both places."
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Add elements that represent the coupling units\n",
    "Now, we add elements to represent the input and output of the P2G and G2P units. They are\n",
    "assigned to specific buses / junctions. The input values have to be set. Since the output is\n",
    "calculated during the simulation, we can simply set it to 0 when calling the `create` function."
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "import pandapower as ppower\n",
    "import pandapipes as ppipes\n",
    "\n",
    "p2g_id_el = ppower.create_load(net_power, bus=3, p_mw=2, name=\"power to gas consumption\")\n",
    "p2g_id_gas = ppipes.create_source(net_gas, junction=1, mdot_kg_per_s=0, name=\"power to gas feed in\")\n",
    "\n",
    "g2p_id_gas = ppipes.create_sink(net_gas, junction=1, mdot_kg_per_s=0.1, name=\"gas to power consumption\")\n",
    "g2p_id_el = ppower.create_sgen(net_power, bus=5, p_mw=0, name=\"fuel cell feed in\")"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Now, the coupling controllers are imported and initialized. We hand over the IDs of the\n",
    "P2G unit in the power grid (i.e., which load element represents the electrolyser)\n",
    "and in the gas grid (i.e., which source elements represents the P2G feed-in).\n",
    "Analogously, a G2P controller is created."
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "from pandapipes.multinet.control.controller.multinet_control import P2GControlMultiEnergy, \\\n",
    "    G2PControlMultiEnergy\n",
    "\n",
    "p2g_ctrl = P2GControlMultiEnergy(multinet, p2g_id_el, p2g_id_gas, efficiency=0.7,\n",
    "                          name_power_net=\"power\", name_gas_net=\"gas\")\n",
    "\n",
    "g2p_ctrl = G2PControlMultiEnergy(multinet, g2p_id_el, g2p_id_gas, efficiency=0.65,\n",
    "                          name_power_net=\"power\", name_gas_net=\"gas\")"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Internally, both controllers calculate with the higher heating value of the gas. (It is a\n",
    "a property of the gas ('fluid') in the gas network and provided in the file\n",
    "*pandapipes/properties/[fluid_name]/higher_heating_value.txt*)\n",
    "\n",
    "It is also possible to order the controllers hierarchical,\n",
    " (cf. the [Control chapter in the pandapower documentation](https://pandapower.readthedocs.io/en/latest/control/control_loop.html))"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Run simulation\n",
    "Now, the simulation can be run. As there are different `run` functions required (power flow or\n",
    "pipe flow), we simply execute `run_control` for the multinet. This collects all nets and\n",
    "controllers and conducts the corresponding run function."
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "from pandapipes.multinet.control.run_control_multinet import run_control\n",
    "run_control(multinet)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Now, the output values have been updated and equal the power input times efficiency (and\n",
    "consideration of unit conversion):"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "print(net_gas.source.loc[p2g_id_gas, 'mdot_kg_per_s'])\n",
    "print(net_power.sgen.loc[g2p_id_el, 'p_mw'])"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "In summary:"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "import pandapipes as ppipes\n",
    "import pandapower as ppower\n",
    "\n",
    "from pandapipes import networks as g_nw\n",
    "from pandapower import networks as e_nw\n",
    "from pandapipes.multinet.create_multinet import create_empty_multinet, add_net_to_multinet\n",
    "from pandapipes.multinet.control.controller.multinet_control import P2GControlMultiEnergy, G2PControlMultiEnergy\n",
    "from pandapipes.multinet.control.run_control_multinet import run_control\n",
    "\n",
    "# get networks:\n",
    "net_power = e_nw.example_simple()\n",
    "net_gas = g_nw.gas_meshed_square()\n",
    "# some adjustments:\n",
    "net_gas.junction.pn_bar = net_gas.ext_grid.p_bar = 30\n",
    "net_gas.pipe.diameter_m = 0.4\n",
    "net_gas.controller.rename(columns={'controller': 'object'}, inplace=True) # due to new version\n",
    "\n",
    "# set fluid:\n",
    "ppipes.create_fluid_from_lib(net_gas, 'hydrogen', overwrite=True)\n",
    "\n",
    "# create multinet and add networks:\n",
    "multinet = create_empty_multinet('tutorial_multinet')\n",
    "add_net_to_multinet(multinet, net_power, 'power')\n",
    "add_net_to_multinet(multinet, net_gas, 'gas')\n",
    "\n",
    "# create elements corresponding to conversion units:\n",
    "p2g_id_el = ppower.create_load(net_power, bus=3, p_mw=2, name=\"power to gas consumption\")\n",
    "p2g_id_gas = ppipes.create_source(net_gas, junction=1, mdot_kg_per_s=0, name=\"power to gas feed in\")\n",
    "\n",
    "g2p_id_gas = ppipes.create_sink(net_gas, junction=1, mdot_kg_per_s=0.1, name=\"gas to power consumption\")\n",
    "g2p_id_el = ppower.create_sgen(net_power, bus=5, p_mw=0, name=\"fuel cell feed in\")\n",
    "\n",
    "# create coupling controllers:\n",
    "p2g_ctrl = P2GControlMultiEnergy(multinet, p2g_id_el, p2g_id_gas, efficiency=0.7,\n",
    "                                 name_power_net=\"power\", name_gas_net=\"gas\")\n",
    "\n",
    "g2p_ctrl = G2PControlMultiEnergy(multinet, g2p_id_el, g2p_id_gas, efficiency=0.65,\n",
    "                                 name_power_net=\"power\", name_gas_net=\"gas\")\n",
    "\n",
    "# run simulation:\n",
    "run_control(multinet)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Time series simulation\n",
    "Sometimes, the input values (and the corresponding outputs) for conversion units change over time,\n",
    "e.g. during a time series simulation. The MultiEnergy controllers themselves cannot handle time\n",
    "series inputs. However, they can easily be combined with a ConstController that updates the input\n",
    " values according to a time series. After the update of the values, the MultiEnergy controller is\n",
    " executed to\n",
    " calculate and write the output value to the other net. The convenience functions to create both\n",
    " controllers in one step are `coupled_p2g_const_control` and `coupled_g2p_const_control`."
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Here is an example for a coupled time series simulation.\n",
    "First, the nets are prepared like before:"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "# prepare just like before\n",
    "net_power = e_nw.example_simple()\n",
    "net_gas = g_nw.gas_meshed_square()\n",
    "net_gas.junction.pn_bar = net_gas.ext_grid.p_bar = 30\n",
    "net_gas.pipe.diameter_m = 0.4\n",
    "net_gas.controller.rename(columns={'controller': 'object'}, inplace=True) # due to new version\n",
    "ppipes.create_fluid_from_lib(net_gas, 'hydrogen', overwrite=True)\n",
    "multinet = create_empty_multinet('tutorial_multinet')\n",
    "add_net_to_multinet(multinet, net_power, 'power_net')\n",
    "add_net_to_multinet(multinet, net_gas, 'gas_net')\n",
    "\n",
    "p2g_id_el = ppower.create_load(net_power, bus=3, p_mw=2, name=\"power to gas consumption\")\n",
    "p2g_id_gas = ppipes.create_source(net_gas, junction=1, mdot_kg_per_s=0, name=\"power to gas feed in\")\n",
    "g2p_id_gas = ppipes.create_sink(net_gas, junction=1, mdot_kg_per_s=0.1, name=\"gas to power consumption\")\n",
    "g2p_id_el = ppower.create_sgen(net_power, bus=5, p_mw=0, name=\"fuel cell feed in\")"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "For the time series, some example data is created and defined as data source."
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "from pandas import DataFrame\n",
    "from numpy.random import random\n",
    "from pandapower.timeseries import DFData\n",
    "\n",
    "def create_data_source(n_timesteps=10):\n",
    "    profiles = DataFrame()\n",
    "    profiles['power to gas consumption'] = random(n_timesteps) * 2 + 1\n",
    "    profiles['gas to power consumption'] = random(n_timesteps) * 0.1\n",
    "    ds = DFData(profiles)\n",
    "\n",
    "    return profiles, ds\n",
    "\n",
    "profiles, ds = create_data_source(10)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Then, output writers are create for the time series simulation:"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "from os.path import join, dirname\n",
    "from pandapower.timeseries import OutputWriter\n",
    "\n",
    "def create_output_writers(multinet, time_steps=None):\n",
    "    nets = multinet[\"nets\"]\n",
    "    ows = dict()\n",
    "    for key_net in nets.keys():\n",
    "        ows[key_net] = {}\n",
    "        if isinstance(nets[key_net], ppower.pandapowerNet):\n",
    "            log_variables = [('res_bus', 'vm_pu'),\n",
    "                             ('res_line', 'loading_percent'),\n",
    "                             ('res_line', 'i_ka'),\n",
    "                             ('res_bus', 'p_mw'),\n",
    "                             ('res_bus', 'q_mvar'),\n",
    "                             ('res_load', 'p_mw'),\n",
    "                             ('res_load', 'q_mvar')]\n",
    "            ow = OutputWriter(nets[key_net], time_steps=time_steps,\n",
    "                              log_variables=log_variables,\n",
    "                              output_path=join(dirname('__file__'),'timeseries', 'results', 'power'),\n",
    "                              output_file_type=\".csv\")\n",
    "            ows[key_net] = ow\n",
    "        elif isinstance(nets[key_net], ppipes.pandapipesNet):\n",
    "            log_variables = [('res_sink', 'mdot_kg_per_s'),\n",
    "                             ('res_source', 'mdot_kg_per_s'),\n",
    "                             ('res_ext_grid', 'mdot_kg_per_s'),\n",
    "                             ('res_pipe', 'v_mean_m_per_s'),\n",
    "                             ('res_junction', 'p_bar'),\n",
    "                             ('res_junction', 't_k')]\n",
    "            ow = OutputWriter(nets[key_net], time_steps=time_steps,\n",
    "                              log_variables=log_variables,\n",
    "                              output_path=join(dirname('__file__'), 'timeseries', 'results', 'gas'),\n",
    "                              output_file_type=\".csv\")\n",
    "            ows[key_net] = ow\n",
    "        else:\n",
    "            raise AttributeError(\"Could not create an output writer for nets of kind \" + str(key_net))\n",
    "    return ows\n",
    "\n",
    "ows = create_output_writers(multinet, 10)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Now, we add the aforementioned combined controllers."
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "from pandapipes.multinet.control.controller.multinet_control import coupled_p2g_const_control, \\\n",
    "    coupled_g2p_const_control\n",
    "coupled_p2g_const_control(multinet, p2g_id_el, p2g_id_gas,\n",
    "                          name_power_net=\"power_net\", name_gas_net=\"gas_net\",\n",
    "                          profile_name='power to gas consumption', data_source=ds,\n",
    "                          p2g_efficiency=0.7)\n",
    "coupled_g2p_const_control(multinet, g2p_id_el, g2p_id_gas,\n",
    "                          name_power_net=\"power_net\", name_gas_net=\"gas_net\",\n",
    "                          element_type_power=\"sgen\",\n",
    "                          profile_name='gas to power consumption', data_source=ds,\n",
    "                          g2p_efficiency=0.65)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "The ConstControllers are stored in the separate nets, while the coupling controllers can be found\n",
    "in the multinet:"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "print(multinet.controller)\n",
    "print(net_power.controller)\n",
    "print(net_gas.controller)\n"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "The time series is calculated with a `run_timeseries` function that has been adapted for multinets:"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "from pandapipes.multinet.timeseries.run_time_series_multinet import run_timeseries\n",
    "run_timeseries(multinet, time_steps=range(10), output_writers=ows)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}